﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using Asteroids.Menu;
using Asteroids.Game.PowerUps;


namespace Asteroids.Game
{
    class Game
    {
        private const int PointsShootAlien = 40;
        private const int PointsShootAsteroid = 20;

        public List<PowerUp> PowerUps { get; set; }
        public List<Projectile> PlayerShotsFired { get; set; }
        public List<EntityBase> ExplosionsOnScreen { get; set; }
        public List<Asteroid> AsteroidsOnScreen { get; set; }
        public List<Alien> AliensOnScreen { get; set; }
        public List<Asteroid> PendingAsteroids { get; set; }

        public Player Player;

        public int CurrentLevel;
		
        private static Random _random;
        public static Random Random
        {
            get { return _random ?? (_random = new Random()); }
        }

        private Game()
        {
            Player = new Player();

            PowerUps = new List<PowerUp>();
            AsteroidsOnScreen = new List<Asteroid>();
            AliensOnScreen = new List<Alien>();
            PlayerShotsFired = new List<Projectile>();
            ExplosionsOnScreen = new List<EntityBase>();
            PendingAsteroids = new List<Asteroid>();

            Player.OnPlayerDeath += OnDeath;
            Player.OnPlayerScoreChanged += PlayerScoreChanged;
			
            CurrentLevel = -1;
        }

        // when ship dies
        private void OnDeath(object sender, EventArgs e)
        {
            MainWindow.Instance.GameScreenUC.Visibility = Visibility.Hidden;
        }

        public void PlayerScoreChanged(object sender, EventArgs args)
        {
            CurrentPlayerScore = Player.Score;
        }

        private int _mostCurrentScore;
        public int CurrentPlayerScore
        {
            get { return _mostCurrentScore; }
            private set
            {
                _mostCurrentScore = value;
                if (OnCurrentScoreChanged != null)
                    OnCurrentScoreChanged(this, EventArgs.Empty);
            }
        }
        public event EventHandler OnCurrentScoreChanged;

        private static Coordinate GetRandomLocation()
        {
            var x = Random.NextDouble()*MainWindow.Instance.ActualWidth;
            var y = Random.NextDouble()*MainWindow.Instance.ActualHeight;
            
            return new Coordinate(x, y);
        }

        public IEnumerable<IRender> GetAllRenderables()
        {
            var renderables = new List<IRender>();
            renderables.AddRange(PowerUps);
            renderables.AddRange(AsteroidsOnScreen);
            renderables.AddRange(AliensOnScreen);
            renderables.AddRange(PlayerShotsFired);
            renderables.AddRange(ExplosionsOnScreen);
            renderables.Add(Player.CurrentShip);

            return renderables;
        }

        public void Update()
        {
            if (IsPaused)
                return;

            // add pending asteroids (from asteroids splitting)
            AsteroidsOnScreen.AddRange(PendingAsteroids);
            PendingAsteroids.Clear();

            // check if the level is over
            if (AsteroidsOnScreen.Count == 0 && AliensOnScreen.Count == 0 && PlayerShotsFired.Count == 0 && PowerUps.Count == 0)
            {
                // set up the next level and return
                NextLevel();
                return;
            }

            var updaters = new List<IUpdate>();

            // populate Updaters
            updaters.AddRange(AsteroidsOnScreen);
            updaters.AddRange(PlayerShotsFired);
            updaters.AddRange(AliensOnScreen);
            updaters.Add(Player);

            // First look for collisions between player shots and asteroids/aliens.););););
            foreach (var shotFired in PlayerShotsFired)
            {
                // check the asteroids
                foreach (var asteroid in AsteroidsOnScreen)
                {
                    if (shotFired.TestCollideWith(asteroid) && !asteroid.Dead && !shotFired.Dead)
                    {
                        shotFired.Collide(new CollideEventArgs(asteroid));
                        asteroid.Collide(new CollideEventArgs(shotFired));
                        Player.Score += PointsShootAsteroid * (CurrentLevel + 1);
                    }
                }
                // check the aliens
                foreach (var alien in AliensOnScreen)
                {
                    if (shotFired.TestCollideWith(alien) && !alien.Dead && !shotFired.Dead)
                    {
                        shotFired.Collide(new CollideEventArgs(alien));
                        alien.Collide(new CollideEventArgs(shotFired));
                        Player.Score += PointsShootAlien * (CurrentLevel + 1);
                    }
                }
            }
            // clear out all the dead ones.
            for (var i = 0; i < ExplosionsOnScreen.Count; i++ )
            {
                if (ExplosionsOnScreen[i].Dead)
                    ExplosionsOnScreen.RemoveAt(i--);
                else
                    ExplosionsOnScreen[i].Dead = true;
            }
            for (var i = 0; i < AsteroidsOnScreen.Count; i++)
            {
                if (AsteroidsOnScreen[i].Dead)
                {
                    AsteroidsOnScreen.RemoveAt(i);
                    i--;
                }
            }
            for (var i = 0; i < AliensOnScreen.Count; i++ )
            {
                if (AliensOnScreen[i].Dead)
                {
                    AliensOnScreen.RemoveAt(i);
                    i--;
                }
            }
            for (var i = 0; i < PlayerShotsFired.Count; i++)
            {
                if (PlayerShotsFired[i].Dead)
                {
                    PlayerShotsFired.RemoveAt(i);
                    i--;
                }
            }
            for (var i = 0; i < PowerUps.Count; i++ )
            {
                if (PowerUps[i].Collected)
                {
                    PowerUps.RemoveAt(i);
                    i--;
                }
            }
            // then look for collisions between the playerShip and asteroids
            foreach (var asteroid in AsteroidsOnScreen)
            {
                if (asteroid.TestCollideWith(Player.CurrentShip))
                {
                    asteroid.Collide(new CollideEventArgs(Player.CurrentShip));
                    Player.Collide(new CollideEventArgs(asteroid));
                }
            }
            // ... playerShip and aliens
            foreach (var alien in AliensOnScreen)
            {
                if (alien.TestCollideWith(Player.CurrentShip))
                {
                    alien.Collide(new CollideEventArgs(Player.CurrentShip));
                    Player.Collide(new CollideEventArgs(alien));
                }
            }

            // look for collisions between the player and powerUps
            foreach (var powerUp in PowerUps)
            {
                if (powerUp.TestCollideWith(Player))
                {
                    powerUp.Collide(new CollideEventArgs(Player));
                }
            }

            // then update things.
            foreach (var updater in updaters)
            {
                updater.Update(new UpdateEventArgs());
            }

            // render the window
            MainWindow.Instance.GameScreenUC.Render(this, new EventArgs());
        }

        public delegate void NextLevelDel(int level);

        public event NextLevelDel NextLevelEvent;

        private void NextLevel()
        {
            CurrentLevel++;
            for (var i = 0; i < Level.GetNumAsteroids(CurrentLevel); i++)
            {
                Asteroid a = Asteroid.CreateAsteroid(Level.GetAsteroidSplitLevel(CurrentLevel), GetRandomLocation);
                while (a.IsWithinOf(150, Player.CurrentShip))
                    a = Asteroid.CreateAsteroid(Level.GetAsteroidSplitLevel(CurrentLevel), GetRandomLocation);
                AsteroidsOnScreen.Add(a);
            }
            for (var i = 0; i < Level.GetNumAliens(CurrentLevel); i++)
            {
                Alien a = Alien.CreateAlien(GetRandomLocation, Level.GetAlienHp(CurrentLevel),
                                            Level.GetAlienSpeed(CurrentLevel));
                while (a.IsWithinOf(200, Player.CurrentShip))
                    a = Alien.CreateAlien(GetRandomLocation, Level.GetAlienHp(CurrentLevel),
                                          Level.GetAlienSpeed(CurrentLevel));
                AliensOnScreen.Add(a);
            }
            if (NextLevelEvent != null)
                NextLevelEvent(CurrentLevel);
        }

        public void Shutdown()
        {
            // shutdown code.
            _instance = null;
        }

        public bool IsPaused;

        private static Game _instance;
        public static Game Instance
        {
            get { return _instance ?? (_instance = new Game()); }
        }

        public void ApplyExplosion(Coordinate location, double radius)
        {
            // create the explosion
            var explosion = new EntityBase
            {
                Location = location,
                Radius = radius,
            };
            // check the asteroids, if it hits, remove them
            for (var i = 0; i < AsteroidsOnScreen.Count; i++ )
            {
                if (explosion.TestCollideWith(AsteroidsOnScreen[i]) && !AsteroidsOnScreen[i].Dead)
                {
                    AsteroidsOnScreen[i].Collide(new CollideEventArgs(explosion));
                    Player.Score += PointsShootAsteroid * (CurrentLevel + 1) * 2;
                }
            }
            // check the aliens, if it hits, remove them
            for (var i = 0; i < AliensOnScreen.Count; i++)
            {
                if (explosion.TestCollideWith(AliensOnScreen[i]) && !AliensOnScreen[i].Dead)
                {
                    AliensOnScreen[i].Collide(new CollideEventArgs(explosion));
                    Player.Score += PointsShootAlien * (CurrentLevel + 1) * 2;
                }
            }

            ExplosionsOnScreen.Add(Nuke.CreateExplosion(location, radius));
        }
    }
}
